// 查找数据域为某一特定值的结点
/**
 * 
- 递归遍历二叉树，若当前遍历到的结点为空，就意味着没找到目标结点，直接返回。
- 若当前遍历到的结点对应的数据域值刚好等于n，则查找成功，返回。
- 若当前遍历到的结点对应的数据域值大于目标值n，则应该在左子树里进一步查找，
设置下一步的遍历范围为 root.left 后，继续递归。
- 若当前遍历到的结点对应的数据域值小于目标值n，则应该在右子树里进一步查找，
设置下一步的遍历范围为 root.right 后，继续递归。
 */
const root = {
  val: 6,
  left: {
    val: 3,
    left: {
      val: 1,
    },
    right: {
      val: 4,
    },
  },
  right: {
    val: 8,
    left: {
      val: 7,
    },
    right: {
      val: 9,
    },
  },
};
function search(root, n) {
  // 若 root 为空，查找失败，直接返回
  if (!root) {
    return;
  }
  // 找到目标结点，输出结点对象
  if (root.val === n) {
    console.log("目标结点是：", root);
  } else if (root.val > n) {
    // 当前结点数据域大于n，向左查找
    search(root.left, n);
  } else {
    // 当前结点数据域小于n，向右查找
    search(root.right, n);
  }
}
console.log(search(root, 8));

// 插入新结点
/** 
二叉搜索树插入结点的过程，和搜索某个结点的过程几乎是一样的:
- 从根结点开始，把我们希望插入的数据值和每一个结点作比较。
- 若大于当前结点，则向右子树探索；
- 若小于当前结点，则向左子树探索。
- 最后找到的那个空位，就是它合理的栖身之所。
*/

function insertIntoBST(root, n) {
  // 若 root 为空，说明当前是一个可以插入的空位
  if (!root) {
    // 用一个值为n的结点占据这个空位
    return {
      val: n,
    };
  }

  if (root.val > n) {
    // 当前结点数据域大于n，向左查找
    root.left = insertIntoBST(root.left, n);
  } else {
    // 当前结点数据域小于n，向右查找
    root.right = insertIntoBST(root.right, n);
  }

  // 返回插入后二叉搜索树的根结点
  return root;
}
console.log(insertIntoBST(root, 5));

// 删除指定结点
/**
 想要删除某个结点，首先要找到这个结点。在定位结点后，我们需要考虑以下情况：
- 结点不存在，定位到了空结点。直接返回即可。
- 需要删除的目标结点没有左孩子也没有右孩子——它是一个叶子结点，删掉它不会对其它结点造成任何影响，直接删除即可。
- 需要删除的目标结点存在左子树，那么就去左子树里寻找小于目标结点值的最大结点，用这个结点覆盖掉目标结点
- 需要删除的目标结点存在右子树，那么就去右子树里寻找大于目标结点值的最小结点，用这个结点覆盖掉目标结点
- 需要删除的目标结点既有左子树、又有右子树，这时就有两种做法了：要么取左子树中值最大的结点，要么取右子树中取值最小的结点。两个结点中任取一个覆盖掉目标结点，都可以维持二叉搜索树的数据有序性
 */

function deleteNode(root, n) {
  // 如果没找到目标结点，则直接返回
  if (!root) {
    return root;
  }
  // 定位到目标结点，开始分情况处理删除动作
  if (root.val === n) {
    // 若是叶子结点，则不需要想太多，直接删除
    if (!root.left && !root.right) {
      root = null;
    } else if (root.left) {
      // 寻找左子树里值最大的结点
      const maxLeft = findMax(root.left);
      // 用这个 maxLeft 覆盖掉需要删除的当前结点
      root.val = maxLeft.val;
      // 覆盖动作会消耗掉原有的 maxLeft 结点
      root.left = deleteNode(root.left, maxLeft.val);
    } else {
      // 寻找右子树里值最小的结点
      const minRight = findMin(root.right);
      // 用这个 minRight 覆盖掉需要删除的当前结点
      root.val = minRight.val;
      // 覆盖动作会消耗掉原有的 minRight 结点
      root.right = deleteNode(root.right, minRight.val);
    }
  } else if (root.val > n) {
    // 若当前结点的值比 n 大，则在左子树中继续寻找目标结点
    root.left = deleteNode(root.left, n);
  } else {
    // 若当前结点的值比 n 小，则在右子树中继续寻找目标结点
    root.right = deleteNode(root.right, n);
  }
  return root;
}

// 寻找左子树最大值
function findMax(root) {
  while (root.right) {
    root = root.right;
  }
  return root;
}

// 寻找右子树的最小值
function findMin(root) {
  while (root.left) {
    root = root.left;
  }
  return root;
}
console.log(deleteNode(root, 3));

// 题目描述：给定一个二叉树，判断其是否是一个有效的二叉搜索树。

/**
 * 思路：
 * 二叉树的定义：
 * 1. 它可以是一棵空树
 * 2. 它可以是一棵由根结点、左子树、右子树组成的树，同时左子树和右子树都是二叉搜索树，
 *    且左子树上所有结点的数据域都小于等于根结点的数据域，右子树上所有结点的数据域都大于等于
 *    根结点的数据域
 */
const isValidBST = function (root) {
  // 定义递归函数
  function dfs(root, minValue, maxValue) {
    // 若是空树，则合法
    if (!root) {
      return true;
    }
    // 若右孩子不大于根结点值，或者左孩子不小于根结点值，则不合法
    if (root.val <= minValue || root.val >= maxValue) return false;
    // 左右子树必须都符合二叉搜索树的数据域大小关系
    return (
      dfs(root.left, minValue, root.val) && dfs(root.right, root.val, maxValue)
    );
  }
  // 初始化最小值和最大值为极小或极大
  return dfs(root, -Infinity, Infinity);
};
console.log(isValidBST(root));

// 题目描述：将一个按照升序排列的有序数组，转换为一棵高度平衡二叉搜索树。
/**
 * 思路：
 * 1、二叉搜索树的特性
 * 二叉搜索树的中序遍历序列是有序的，题中所给的数组也是有序的，
 * 因此我们可以认为题目中给出的数组就是目标二叉树的中序遍历序列。
 * 2、平衡二叉树的特性
 * 一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。
 *
 * 我们发现“以中间元素为根结点，将数组提成树”这种操作，可以保证根结点左右两侧的子树高度绝对值不大于1。
 * 要想保证每一棵子树都满足这个条件，
 * 我们只需要对有序数组的每一个对半分出来的子序列都递归地执行这个操作即可。
 */
function TreeNode(data) {
  this.data = data;
  this.left = null;
  this.right = null;
}
const sortedArrayToBST = function (nums) {
  // 处理边界条件
  if (!nums.length) {
    return null;
  }

  // root 结点是递归“提”起数组的结果
  const root = buildBST(0, nums.length - 1);

  // 定义二叉树构建函数，入参是子序列的索引范围
  function buildBST(low, high) {
    // 当 low > high 时，意味着当前范围的数字已经被递归处理完全了
    if (low > high) {
      return null;
    }
    // 二分一下，取出当前子序列的中间元素
    const mid = Math.floor(low + (high - low) / 2);
    // 将中间元素的值作为当前子树的根结点值
    const cur = new TreeNode(nums[mid]);
    // 递归构建左子树，范围二分为[low,mid)
    cur.left = buildBST(low, mid - 1);
    // 递归构建左子树，范围二分为为(mid,high]
    cur.right = buildBST(mid + 1, high);
    // 返回当前结点
    return cur;
  }
  // 返回根结点
  return root;
};
console.log(sortedArrayToBST([-10,-3,0,5,9]));
